(function(window){
    var jShen = (function(){
    
        var jShen = function(expr, context, dom){
            return new jShen.fn.init(expr, context, dom);
        }, document = window.document, location = window.location, toString = Object.prototype.toString, regexTrim = /^\s+|\s+$/g, regexDomId = /^#(\w+)$/, regexDomString = /^(<.+>)$/, regexDomSingleTag = /^<(\w+)\s*\/?>(?:<\/\1>)?$/, regexDomObjectTypePrefix = /^\[object HTML/, regexBasicSelector = /^([#.]?)(\w+)((?:\[\w+=['"]\w+?['"]\])+)?/, class2type = {};
        
        jShen.fn = {
            constructor: jShen,
            init: function(selector, context, dom){
                // null, undefined, ''
                if (!selector) {
                    return this;
                }
                // dom element
                if (selector.nodeType) {
                    this.context = this[0] = selector;
                    this.length = 1;
                    return this;
                }
                // trim selector
                if (typeof selector === 'string') 
                    selector = selector.trim;
                // optimize body
                if (selector === 'body' && document.body) {
                    this.context = document;
                    this[0] = document.body;
                    this.selector = selector;
                    this.length = 0;
                    return this;
                }
                
                var elem, match;
                // select by id
                if (match = regexDomId.exec(selector)) {
                    elem = document.getElementById(match[1]);
                    if (elem) {
                        this[0] = elem;
                        this.length = 1;
                    }
                    this.context = document;
                    this.selector = selector;
                    return this;
                }
                // string dom element
                if (regexDomString.test(selector)) {
                    if (context instanceof jShen) 
                        context = context[0];
                    doc = (context ? context.ownerDocument || context : document);
                    
                    if (regexDomString.test(selector)) {
                        // TODO
                    }
                    else {
                        // TODO
                    
                    }
                }
            },
            each: function(fn, list){
                return jShen.each(fn, list);
            },
            jShen: "0.2.0",
            // default selector
            selector: "",
            // default length
            length: 0,
            size: function(){
                return this.length;
            },
            push: Array.prototype.push,
            splice: Array.prototype.splice
        };
        jShen.fn.init.prototype = jShen.prototype = jShen.fn;
        
        jShen.extend = jShen.fn.extend = function(){
            var options, target = this;
            
            for (var i = 0, length = arguments.length; i < length; i++) {
                if ((options = arguments[i]) != null) {// not null/undefined
                    for (name in options) {
                        target[name] = options[name];
                    }
                }
            }
            return this;
        };
        
        ///
        // common
        ///
        jShen.extend({
            error: function(msg){
                throw msg;
            },
            trim: function(str){
                return str ? str.replace(regexTrim, '') : '';
            },
            merge: function(obj1, obj2, replace){
                replace = typeof replace === 'boolean' ? replace : true;
                if (typeof obj2 === 'undefined') {
                    obj2 = obj1;
                    obj1 = this;
                }
                switch (jShen.type(obj1)) {
                    case 'array':
                        return jShen.mergeArray(obj1, obj2, replace);
                    case 'object':
                        return jShen.mergeObject(obj1, obj2, replace);
                }
            },
            mergeArray: function(obj1, obj2, replace){
                var i = obj1.length, j = 0;
                if (typeof obj2.length === 'number') {
                    for (var l = obj2.length; j < l; j++) {
                        if (replace || typeof obj[i] === 'undefined') {
                            obj1[i++] = obj2[j];
                        }
                    }
                }
                else {
                    while (typeof obj2[j] !== 'undefined') {
                        if (replace || typeof obj[i] === 'undefined') {
                            obj1[i++] = obj2[j];
                        }
                        j++;
                    }
                }
                obj1.length = i;
                return obj1;
            },
            mergeObject: function(obj1, obj2, replace){
                for (name in obj2) {
                    if (obj2.hasOwnProperty(name) &&
                    (replace || typeof obj1[name] === 'undefined')) {
                        obj1[name] = obj2[name];
                    }
                }
                return obj1;
            },
            type: function(obj){
                return obj == null ? String(obj) : class2type[toString.call(obj)] || 'object';
            },
            each: function(fn, list){
                if (!list) {
                    list = this;
                }
                for (var i = 0, l = list.length; i < l; i++) {
                    if (fn.call(list[i], i, list[i]) === false) {
                        break;
                    }
                }
                return list;
            },
            clone: function(obj){
                if (obj == null || typeof(obj) != 'object') 
                    return obj;
                var temp = obj.constructor(); // changed
                for (var key in obj) 
                    temp[key] = clone(obj[key]);
                return temp;
            },
            parseJSON: function(str){
                if (!str || typeof str !== 'string') {
                    return null;
                }
                str = jShen.trim(str);
                
                if (window.JSON && window.JSON.parse) 
                    return window.JSON.parse(str);
                
                jShen.error('Invalid JSON: ' + str);
            },
            parseXML: function(str){
                // TODO
            },
            hashcode: function(obj){
                var hashcode = 0, str;
                str = jShen.serialize(obj);
                for (var i = 0, l = str.length; i < l; i++) 
                    hashcode = hashcode * 31 + str.charCodeAt(i);
                if (hashcode > 4294967295) 
                    hashcode = hashcode % 4294967295;
                if (hashcode < 2147483647) 
                    return hashcode.toString();
                else 
                    return (-2147483648 + hashcode % 2147483647).toString();
            },
            serialize: function(obj){
                var type = jShen.type(obj), str = '[' + type + ']';
                switch (type) {
                    case 'object':
                        str += '{';
                        for (var name in obj) {
                            str += name + ':' + jShen.serialize(obj[name]) + ',';
                        }
                        str = (str.charAt(str.length - 1) === ',' ? str.slice(0, -1) : str) + '}';
                        break;
                    default:
                        str += obj.toString();
                }
                return str;
            }
        });
        
        jShen.fn.extend({
            clear: function(){
                this.splice(0);
                return this;
            },
			each: function(fn){
				jShen.each(fn, this);
			},
            unique: function(){
                // TODO
            }
        });
        
        // add class2type	
        jShen.each(function(key, value){
            class2type['[object ' + value + ']'] = value.toLowerCase();
        }, ['String', 'Boolean', 'Number', 'Function', 'Array', 'Date', 'RegExp', 'Object']);
        
        ///
        // Dom
        ///
        jShen.fn.extend({
            parent: function(level){
                level = level || 1;
                var i, parent, list = new jShen();
                this.each(function(index, value){
                    parent = value;
                    for (i = 0; i < level; i++) 
                        parent = parent.parentNode;
                    list.push(parent);
                });
                return list.unique();
            },
            next: function(level){
                level = level || 1;
                var i, next, list = new jShen();
                this.each(function(index, value){
                    next = value;
                    for (i = 0; i < level; i++) {
                        do {
                            next = next.nextSibling;
                        }
                        while (next && !regexDomObjectTypePrefix.test(next.toString));
                    }
                    list.push(next);
                });
                return list.unique();
            },
            prev: function(level){
                level = level || 1;
                var i, prev, list = new jShen();
                this.each(function(index, value){
                    prev = value;
                    for (i = 0; i < level; i++) {
                        do {
                            prev = prev.previousSibling;
                        }
                        while (prev && !regexDomObjectTypePrefix.test(prev.toString));
                    }
                    list.push(prev);
                });
                return list.unique();
            },
            find: function(expr){
                // TODO
                var list = new jShen();
                this.each(function(index, value){
                    list.merge(new jShen(expr, value));
                });
                return list.unique();
            },
            filter: function(expr){
                var list = new jShen();
                this.each(function(index, value){
                    var group = regexBasicSelector.exec(expr);
                    var attrs;
                    if (group !== null) {
                        // attrs
						var attrs
                        if (group[3]) {
                            // TODO [xxx="abc"][yyy="def"]
							var attrJson = group[3].replace(/\[(?=\w)/g, '["').replace(/=\s*"/g, '":"').replace('\]\[', ',').replace('\[','{').replace('\]','}');
                            attrs = jShen.parseJSON(attrJson);
                        }
                        switch (group[1]) {
                            case '#':
                                if (value.id === group[2]) {
                                    list.push(value);
                                    return false;
                                }
                                break;
                            case '.':
                                if (value.className === group[2]) {
                                    list.push(value);
                                }
                                break;
                            case '':
                                if (value.tagName.toLowerCase() === group[2]) {
                                    list.push(value);
                                }
                                break;
                        }
                    }
                });
                return list;
            },
            addClass: function(clazz){
				this.each(function(index, value){
					var reg = new RegExp('(?:^| )' + clazz + '(?:$| )');
					if (!reg.test(value.className)) {
						value.className = jShen.trim(value.className) + ' ' + clazz;
					}
				});
				return this;
            },
            removeClass: function(){
				this.each(function(index, value){
					var reg = new RegExp('(?:^| )' + clazz + '(?:$| )', 'g');
					value.className = jShen.trim(value.className.replace(reg, ' '));
				});
				return this;
            },
            hasClass: function(){
				var has = false;
				this.each(function(index, value){
					var reg = new RegExp('(?:^| )' + clazz + '(?:$| )', 'g');
					if (reg.test(value.className)) {
						has = true;
						return false;
					}
				});
				return has;
            },
            css: function(name, value){
				if (typeof value === 'undefined') {
					if (jShen.type(name) === 'string') {
						// getter
						
					} else {
						// setter object
					}
				} else {
					// setter
					this.each(function(index, item){
						item.style[name] = value;
					});
				}
				return this;
            },
            attr: function(){
            },
            val: function(){
            },
            html: function(){
            }
        });
        
        ///
        // event
        ///
        jShen.fn.extend({
            bind: function(){
            },
            unbind: function(){
            },
            click: function(){
            }
        });
        
        ///
        // ajax
        ///
        var ajaxLocation;
        try {
            ajaxLocation = location.href;
        } 
        catch (e) {
            ajaxLocation = document.createElement('a');
            ajaxLocation.href = '';
            ajaxLocation = ajaxLocation.href;
        }
        
        jShen.extend({
            ajaxSettings: {
                url: ajaxLocation,
                type: 'GET',
                dataType: '*',
                data: null,
                contentType: 'application/x-www-form-urlencoded',
                async: true,
                accepts: {
                    xml: 'application/xml, text/xml',
                    html: 'text/html',
                    text: 'text/plain',
                    json: 'application/json, text/javascript',
                    '*': '*/*'
                },
                converters: {
                    "* text": window.String,
                    "text html": true,
                    "text json": jShen.parseJSON,
                    "text xml": jShen.parseXML
                }
            },
            ajaxStateChange: function(reqObj){
                console.log('readyState: ' + reqObj.request.readyState);
                console.log('status: ' + reqObj.request.status);
                if (reqObj.request.readyState === 4) {
                    if (reqObj.request.status === 200 || reqObj.request.status === 304) {// 200: Success, 304: NotModified
                        // success callback function
                        if (reqObj.success) {
                            reqObj.success.call(reqObj.request, reqObj.request.responseText, reqObj.request, reqObj.request.statusText);
                        }
                    }
                    else {
                        // error callback function
                        if (reqObj.error) {
                            reqObj.error.call(reqObj.request, reqObj.request, reqObj.request.statusText);
                        }
                    }
                    // complete callback function
                    if (reqObj.complete) {
                        reqObj.complete.call(reqObj.request, reqObj.request, reqObj.request.statusText);
                    }
                }
            },
            ajaxGetRequest: function(){
                return window.XMLHttpRequest ? new XMLHttpRequest() : window.ActiveXObject ? new ActiveXObject("Microsoft.XMLHTTP") : null;
            },
            ajax: function(input){
                var reqObj = input;
                jShen.merge(reqObj, jShen.ajaxSettings, false);
                reqObj.type = reqObj.type.toUpperCase();
                reqObj.dataType = jShen.ajaxSettings.accepts[reqObj.dataType];
                
                reqObj.request = new XMLHttpRequest();
                reqObj.request.onreadystatechange = function(){
                    return jShen.ajaxStateChange.call(reqObj, reqObj);
                };
                reqObj.request.open(reqObj.type, reqObj.url, true);
                reqObj.request.send(reqObj.data);
            }
        });
        
        return jShen;
    })();
    
    window.jShen = window._ = jShen;
})(window);
